package com.wl.dzt.sys.util.clazz;

import com.wl.dzt.sys.annotation.Transient;
import com.wl.dzt.sys.util.CollectionUtils;
import com.wl.dzt.sys.util.FieldUtil;
import com.wl.dzt.sys.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.Timestamp;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author wl_code@163.com
 * @version 1.0
 * @date 2018/7/5/005
 * @description 类工具
 * 封装了一些类的操作
 */
public class ClassUtil {

    public static final Logger log = LoggerFactory.getLogger(ClassUtil.class);

    private final static String CLASSES_DIC = "classes", BOOT_CLASSES_DIC = "classes!/",

    /**
     * 类分隔符
     */
    SEPARATOR = "[/]{1,}|(\\\\){1,}",

    /**
     * 关联jar包后缀
     */
    SUFFIX = ".jar!/", BOOT_SUFFIX = "!/BOOT-INF/classes!/";

    private static Map<String, String> componentMap = new HashMap<>();

    static {
        componentMap.putAll(ResourceUtils.loadProperties("component-list.properties"));
    }


    /**
     * 获取类路径和指定jar包下的类
     *
     * @return 关联类集合
     */
    private static List<Class<?>> findAllFallClass(String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        try {
            String paths[] = getAllResoucesCompatible();
            for (String path : paths) {
                // 是累路径下的文件添加类
                if (path.endsWith(CLASSES_DIC) || path.endsWith(BOOT_CLASSES_DIC))
                    classes.addAll(getClasses(path, packageName));
//                 是指定后缀的jar包进行jar包扫描
                else if (path.endsWith(SUFFIX) && isComponentPath(path, componentMap)) {
//                    String jarPath = new StringBuffer(path).deleteCharAt(path.length() - 1).delete(0, 5).toString();
                    classes.addAll(getClasssFromJarFileCompatible(path, packageName));

                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classes;
    }


    private static boolean isComponentPath(String path, Map<String, String> componentMap) {
        boolean flag = false;
        for (Map.Entry<String, String> map : componentMap.entrySet()) {
            if (path.indexOf(map.getKey()) != -1) {
                flag = true;
                break;
            }
        }
        return flag;
    }

    /**
     * 类遍历,遍历某个包下的所有类并且执行逻辑
     *
     * @param packageName
     * @param classEachHandler
     */
    public static void eachClasses(String packageName, ClassEachHandler classEachHandler) {
        findAllFallClass(packageName).forEach(classEachHandler::handler);
    }

    /**
     * 判断是否为值类型
     *
     * @param clazz
     * @return
     */
    public static Boolean isBaseValType(Class<?> clazz) {
        return clazz == String.class || clazz == Integer.class || clazz == Double.class || clazz == Date.class
                || clazz == Timestamp.class || clazz == Set.class || clazz == List.class;
    }

    /**
     * 根据字符串去创建一个类
     *
     * @param className
     * @return
     */
    public static Class<?> createClass(String className) {
        if (className == null) {
            return null;
        }
        try {
            Class<?> clazz = Class.forName(className);
            return clazz;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 根据类路径获得类
     *
     * @param filePath
     * @return
     */
    private static List<Class<?>> getClasses(String filePath, String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        if(filePath.endsWith(BOOT_SUFFIX)){
            classes.addAll(getClasssFromJarFileCompatible(filePath, packageName));
        }else{
            getClassName(filePath, null, packageName).forEach(className -> {
                try {
                    Class<?> clazz = Class.forName(className);
                    // 不是接口则添加
                    if (!clazz.isInterface()) {
                        classes.add(clazz);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        return classes;
    }


    /**
     * 构建类实例
     *
     * @param clazz
     * @return
     */
    public static Object newInstance(Class<?> clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 遍历指定包下所有的类
     * 兼容springboot生产环境
     *
     * @param filePath
     * @param className
     * @param packageName
     * @return
     */
    private static List<String> getClassName(String filePath, List<String> className, String packageName) {
        if (filePath.endsWith(BOOT_CLASSES_DIC)) {
            filePath = filePath.substring(0, filePath.length() - 2);
        }
        List<String> myClassName = new ArrayList<>();
        File file = null;
        try {
            file = new File(URLDecoder.decode(filePath, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        File[] childFiles = file.listFiles();
        //TODO springboot通过这种方式找不着对应的class.
        if (childFiles != null) {
            for (File childFile : childFiles) {
                if (!childFile.isHidden()) {
                    if (childFile.isDirectory()) {
                        myClassName.addAll(getClassName(childFile.getPath(), myClassName, packageName));
                    } else {
                        if (childFile.getPath().endsWith(".class")) {
                            String childFilePath = childFile.getPath();
                            childFilePath = childFilePath.substring(childFilePath.indexOf("classes") + 8, childFilePath.lastIndexOf("."))
                                    .replace("/", ".").replace("\\", ".");
                            myClassName.add(childFilePath);
                        }
                    }
                }
            }
        }
        return myClassName;
    }

    /**
     * 将某个类中的静态字段转换为map
     *
     * @param clazz
     * @return
     */
    public static Map<String, Object> propertySataticClass(Class<?> clazz) {
        Map<String, Object> map = new HashMap<>();
        Arrays.stream(clazz.getDeclaredFields()).forEach(field -> {
            if (Modifier.isStatic(field.getModifiers())) {
                try {
                    map.put(field.getName(), field.get(clazz));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        return map;
    }

    /**
     * 获取某个实体类瞬时态注解的数量
     *
     * @param clazz
     * @return
     */
    public static int transientCount(Class<?> clazz) {
        int i = 0;
        for (Field field : clazz.getDeclaredFields()) {
            Transient tr = field.getAnnotation(Transient.class);
            if (tr != null)
                i++;
        }
        return i;
    }


    /**
     * 根据当前实体字段是否有值判断字段实际数量
     *
     * @param entity
     * @return
     */
    public static int transientCountByObject(Object entity) {
        int i = 0;
        for (Field field : entity.getClass().getDeclaredFields()) {
            Transient tr = field.getAnnotation(Transient.class);
            Object val = FieldUtil.doGetMethod(entity, field.getName());
            if (tr != null && val != null)
                i++;
        }
        return i;
    }


    /**
     * 静态类属性值通过map设置
     *
     * @param map
     * @param clazz
     */
    public static void sataticClassForProperty(Map<String, Object> map, Class<?> clazz) {
        Arrays.stream(clazz.getDeclaredFields()).forEach(field -> {
            if (Modifier.isStatic(field.getModifiers())) {
                try {
                    Object val = map.get(field.getName());
                    if (val != null) {
                        field.setAccessible(true);
                        field.set(clazz, val);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 得到指定jar包中文件的类
     *
     * @param jarPath jar包路径
     * @return 类集合
     */
    private static List<Class<?>> getClasssFromJarFile(String jarPath, String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        try {
            JarFile jarFile = new JarFile(jarPath);
            Enumeration<JarEntry> jes = jarFile.entries();
            addClass(classes, jes, packageName);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return classes;
    }


    /**
     * 得到指定jar包中文件的类
     * 兼容springboot生产环境
     *
     * @param jarPath jar包路径
     * @return 类集合
     */
    private static List<Class<?>> getClasssFromJarFileCompatible(String jarPath, String packageName) {
        List<Class<?>> classes = new ArrayList<>();
        JarFile jarFile = null;
        try {
            URL url = new URL(jarPath);
            JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
            jarFile = jarURLConnection.getJarFile();
            Enumeration<JarEntry> jes = jarFile.entries();
            addClass(classes, jes, packageName);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jarFile != null) {
                try {
                    jarFile.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return classes;
    }


    private static void addClass(List<Class<?>> classes, Enumeration<JarEntry> jes, String packageName) throws ClassNotFoundException {
        while (jes.hasMoreElements()) {
            JarEntry jarEntry = jes.nextElement();
            // 只取class
            if (jarEntry.getName().endsWith(".class")) {
                String className = jarEntry.getName().replaceAll(SEPARATOR, ".").replaceAll("BOOT-INF.classes.", "");
                // 只取文件名字作为类名,后缀的.class 长度为6所以减6
                className = className.substring(0, className.length() - 6);
                if (className.indexOf(packageName) != -1) {
                    try {
                        Class<?> clazz = Class.forName(className);
                        if (clazz != null && !clazz.isInterface()) {
                            classes.add(clazz);
                        }
                    } catch (NoClassDefFoundError e) {
                        log.warn(String.format("class[%s] is not found", className));
                    }
                }

            }
        }
    }

    /**
     * 获的所有的资源.只获取文件类型和jar包类型的资源
     *
     * @return 资源数组
     * @throws IOException io异常
     */
    private static String[] getAllResouces() throws IOException {
        List<String> list = new ArrayList<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> urls = loader.getResources("");
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url != null) {
                // 必须编码路径防止中文路径获取不到文件
                String protocol = url.getProtocol(), pkgPath = URLDecoder.decode(url.getPath(), "UTF-8"), path = new StringBuffer(
                        pkgPath).deleteCharAt(pkgPath.length() - 1).toString();
                if ("file".equalsIgnoreCase(protocol)) {
                    list.add(path);
                }
            }
        }
        return CollectionUtils.toArray(list, String.class);
    }

    /**
     * 兼容springboot 生产环境
     *
     * @return
     * @throws IOException
     */
    private static String[] getAllResoucesCompatible() throws IOException {
        List<String> list = new ArrayList<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> urls = loader.getResources("");
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url != null) {
                // 必须编码路径防止中文路径获取不到文件
                String protocol = url.getProtocol(), pkgPath = URLDecoder.decode(url.getPath(), "UTF-8"), path = new StringBuffer(
                        pkgPath).deleteCharAt(pkgPath.length() - 1).toString();
                if ("file".equalsIgnoreCase(protocol)) {
                    list.add(path);
                }

                if ("jar".equalsIgnoreCase(protocol)) {
                    list.add(url.toString());
                }
            }
        }
        return CollectionUtils.toArray(list, String.class);
    }

}
